package com.by.rpc.transaction;

import com.by.rpc.util.RedisLuaHelper;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 基于 Redis 的分布式锁（适配多序列化方式）
 * 支持自动续期和可重入功能
 */
@Slf4j
@Component
public class RedisDistributedLock {
    
    // Lua脚本，用于原子性地释放锁
    private static final String UNLOCK_LUA_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "return redis.call('del', KEYS[1]) " +
            "else return 0 end";
    
    // Lua脚本，用于原子性地续期锁
    private static final String RENEW_LUA_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "return redis.call('expire', KEYS[1], ARGV[2]) " +
            "else return 0 end";
    
    // Lua脚本：扣减库存
    private static final String DECR_STOCK_LUA = """
            local current = redis.call('get', KEYS[1])
            if not current then return -1 end
            local current_num = tonumber(current)
            if not current_num then return -1 end
            local quantity_num = tonumber(ARGV[1])
            if not quantity_num then return -1 end
            if current_num >= quantity_num then
                return redis.call('decrby', KEYS[1], quantity_num)
            else
                return -1
            end
            """;

    private final DefaultRedisScript<Long> unlockScript;
    private final DefaultRedisScript<Long> renewScript;
    private static final DefaultRedisScript<Long> DECR_STOCK_SCRIPT;

    // RedisLuaHelper用于执行Lua脚本
    private final RedisLuaHelper redisLuaHelper;

    // 存储当前线程持有的锁信息，支持可重入
    private final ThreadLocal<LockInfo> lockInfoThreadLocal = new ThreadLocal<>();
    
    // 存储锁的续期任务
    private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
    
    // 续期任务映射
    private final ConcurrentHashMap<String, ScheduledFuture<?>> renewTasks = new ConcurrentHashMap<>();
    
    private final RedisTemplate<String, Object> redisTemplate;

    static {
        // 初始化释放锁脚本
        DECR_STOCK_SCRIPT = new DefaultRedisScript<>();
        DECR_STOCK_SCRIPT.setScriptText(DECR_STOCK_LUA);
        DECR_STOCK_SCRIPT.setResultType(Long.class);
    }

    public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate, RedisLuaHelper redisLuaHelper) {
        this.redisTemplate = redisTemplate;
        this.redisLuaHelper = redisLuaHelper;
        this.unlockScript = new DefaultRedisScript<>();
        this.unlockScript.setScriptText(UNLOCK_LUA_SCRIPT);
        this.unlockScript.setResultType(Long.class);
        
        this.renewScript = new DefaultRedisScript<>();
        this.renewScript.setScriptText(RENEW_LUA_SCRIPT);
        this.renewScript.setResultType(Long.class);
    }

    /**
     * 获取分布式锁
     * @param lockKey 锁的key
     * @param expireTime 过期时间(秒)
     * @return 锁标识，用于释放锁
     */
    public String tryLock(String lockKey, long expireTime) {
        try {
            // 检查当前线程是否已经持有该锁（可重入）
            LockInfo lockInfo = lockInfoThreadLocal.get();
            if (lockInfo != null && lockKey.equals(lockInfo.getLockKey())) {
                // 增加重入计数
                lockInfo.incrementReentrantCount();
                log.debug("Thread reentered lock: {}, reentrant count: {}", lockKey, lockInfo.getReentrantCount());
                return lockInfo.getLockValue();
            }
            
            String lockValue = UUID.randomUUID().toString();
            Boolean success = redisTemplate.opsForValue()
                    .setIfAbsent(lockKey, lockValue, expireTime, TimeUnit.SECONDS);
            if (Boolean.TRUE.equals(success)) {
                log.debug("获取锁成功: {}", lockKey);
                // 记录锁信息
                lockInfo = new LockInfo(lockKey, lockValue, expireTime);
                lockInfoThreadLocal.set(lockInfo);
                
                // 启动自动续期任务（续期时间为过期时间的一半）
                startRenewTask(lockKey, lockValue, expireTime);
                return lockValue;
            }
            return null;
        } catch (Exception e) {
            log.error("获取锁失败: {}", lockKey, e);
            return null;
        }
    }

    /**
     * 释放分布式锁
     * @param lockKey 锁的key
     * @param lockValue 锁的值
     * @return 是否释放成功
     */
    public boolean releaseLock(String lockKey, String lockValue) {
        try {
            // 检查当前线程是否持有该锁
            LockInfo lockInfo = lockInfoThreadLocal.get();
            if (lockInfo == null || !lockKey.equals(lockInfo.getLockKey())) {
                log.warn("Current thread does not hold the lock: {}", lockKey);
                return false;
            }
            
            // 减少重入计数
            int reentrantCount = lockInfo.decrementReentrantCount();
            if (reentrantCount > 0) {
                log.debug("Lock is still reentrant, count: {}", reentrantCount);
                return true;
            }
            
            // 停止续期任务
            stopRenewTask(lockKey);
            
            // 使用Lua脚本原子性地释放锁
            Long result = redisLuaHelper.executeObjectLua(unlockScript, Collections.singletonList(lockKey), lockValue);
            boolean success = Long.valueOf(1).equals(result);
            if (success) {
                log.debug("Successfully released lock for key: {}", lockKey);
                // 清除线程本地变量
                lockInfoThreadLocal.remove();
            } else {
                log.warn("Failed to release lock for key: {}, lock may not exist or held by another thread", lockKey);
            }
            return success;
        } catch (Exception e) {
            log.error("Failed to release lock for key: {}", lockKey, e);
            return false;
        }
    }
    
    /**
     * 启动锁的自动续期任务
     * @param lockKey 锁的key
     * @param lockValue 锁的值
     * @param expireTime 过期时间(秒)
     */
    private void startRenewTask(String lockKey, String lockValue, long expireTime) {
        // 续期时间设为过期时间的一半
        long renewPeriod = expireTime / 2;
        if (renewPeriod <= 0) {
            renewPeriod = 1;
        }
        
        ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(
                () -> renewLock(lockKey, lockValue, expireTime),
                renewPeriod,
                renewPeriod,
                TimeUnit.SECONDS
        );
        
        // 存储续期任务引用
        renewTasks.put(lockKey, scheduledFuture);
        log.debug("Started renew task for lock: {}, renew period: {} seconds", lockKey, renewPeriod);
    }
    
    /**
     * 停止锁的自动续期任务
     * @param lockKey 锁的key
     */
    private void stopRenewTask(String lockKey) {
        ScheduledFuture<?> scheduledFuture = renewTasks.remove(lockKey);
        if (scheduledFuture != null) {
            scheduledFuture.cancel(true);
            log.debug("Stopped renew task for lock: {}", lockKey);
        }
    }
    
    /**
     * 续期锁的过期时间
     * @param lockKey 锁的key
     * @param lockValue 锁的值
     * @param expireTime 过期时间(秒)
     */
    private void renewLock(String lockKey, String lockValue, long expireTime) {
        try {
            Long result = redisLuaHelper.executeObjectLua(renewScript, Collections.singletonList(lockKey), lockValue, String.valueOf(expireTime));
            if (Long.valueOf(1).equals(result)) {
                log.debug("Successfully renewed lock: {}", lockKey);
            } else {
                log.warn("Failed to renew lock: {}, lock may have been released", lockKey);
                // 停止续期任务
                stopRenewTask(lockKey);
            }
        } catch (Exception e) {
            log.error("Failed to renew lock: {}", lockKey, e);
        }
    }

    /**
     * 原子性地扣减库存
     * @param stockKey 库存key
     * @param quantity 扣减数量
     * @return 扣减后剩余库存，-1表示库存不足
     */
    public int decrementStock(String stockKey, int quantity) {
        try {
            Long result = redisLuaHelper.executeObjectLua(DECR_STOCK_SCRIPT, Collections.singletonList(stockKey), String.valueOf(quantity));
            int value = result != null ? result.intValue() : -1;
            if (value == -1) {
                log.debug("Stock not enough for key: {}", stockKey);
            } else {
                log.debug("Stock decremented, remaining: {}", value);
            }
            return value;
        } catch (Exception e) {
            log.error("Decrement stock failed: {}", stockKey, e);
            return -1;
        }
    }

    /**
     * 增加库存
     */
    public int incrementStock(String stockKey, int quantity) {
        try {
            long result = redisTemplate.boundValueOps(stockKey).increment(quantity);
            log.debug("Incremented stock: {}, new value: {}", stockKey, result);
            return (int) result;
        } catch (Exception e) {
            log.error("Increment stock failed: {}", stockKey, e);
            return -1;
        }
    }
    
    /**
     * 锁信息类，用于存储锁的相关信息
     */
    @Data
    private static class LockInfo {
        private final String lockKey;
        private final String lockValue;
        private final long expireTime;
        private final AtomicInteger reentrantCount = new AtomicInteger(1);
        
        public LockInfo(String lockKey, String lockValue, long expireTime) {
            this.lockKey = lockKey;
            this.lockValue = lockValue;
            this.expireTime = expireTime;
        }



        public int getReentrantCount() {
            return reentrantCount.get();
        }

        public int incrementReentrantCount() {
            return reentrantCount.incrementAndGet();
        }

        public int decrementReentrantCount() {
            return reentrantCount.decrementAndGet();
        }
    }
}